<?php

class ConfigPhp
{
	/**
	 * Parser of PHP source files.
	 *
	 * @var mixed
	 */
	var $parser = NULL;
	var $source = NULL;
	var $phpdoc = NULL;

	/**
	 * hash ( config_param => value ).
	 *
	 * @var mixed
	 * @access public
	 */
	var $keys = NULL;
	var $phpdoc_keys = NULL;

	var $desc = NULL;
	var $ready = false;

	public function __construct ($config=array(), $init=true)
	{
		foreach ($config as $k=>&$v) $this->$k = $v;
		if ($init) $this->init();
	} // end of __construct
	public function init ($force=false)
	{
		if ($this->ready && !$force) return true; $this->ready = false;

		do {
			// test for phpdoc
			if (!$this->phpdoc) {
				// test for source config file
				if (!isset($this->source) || !file_exists($this->source)) {
					trigger_error('No source file', E_USER_WARNING);
					break;
				}

				// test for parser
				if (!isset($this->parser)) {
					if (!class_exists('PhpDocParser')) require_once 'PhpDocParser.php';
					$this->parser = new PhpDocParser();
				}

				// read config.php
				$this->phpdoc = $this->parser->parse($this->source);

				if (!$this->phpdoc) {
					trigger_error('No phpdoc', E_USER_NOTICE);
					break;
				}
			}

			// for named config parameters:
			//   fill hash $this->keys ( config_param => value )
			//   fill hash $this->phpdoc_keys ( config_param => phpdoc )
			$this->keys = array();
			$this->phpdoc_keys = array();
			foreach ($this->phpdoc['docs'] as &$doc) {
				// check has name
				$tname = PhpDocUtils::findFirstTagByName('name', $doc);
				if (!$tname) continue;

				// check just for presence
				$tvalue = PhpDocUtils::findFirstTagByName('value', $doc);
				if (!$tvalue) continue;

				$field = array();
				$name = $tname['value'];
				if (preg_match('#^\$[[:alnum:]]+\s*\[\s*([\'"]?)(.*?)\1\s*]$#', $name, $ms)) {
					$name = $ms[2];
				}

				$this->keys[$name] = PhpDocUtils::extractPhpValue($tvalue['value']);
				$this->phpdoc_keys[$name] = $doc;
			}

			$this->ready = true;
		} while (false);

		return $this->ready;
	} // end of method init

	public function getParam ($key)
	{
		return $this->keys[$key];
	} // end of method get
	public function setParam ($key, $value)
	{
		// Запомним новое значение параметра, и тут же запишем в файл.
		//
		// Новые ключи создавать не умеем.
		if (array_key_exists($key, $this->keys)) {
			// строка для нового значения в формате PHP
			if ( true === $value ) {
				$svalue = 'true';
			} else if ( false === $value ) {
				$svalue = 'false';
			} else {
				$svalue = PhpDocUtils::buildPhpValue($value);
			}
			// запишем строку в файл
			$doc = $this->phpdoc_keys[$key];
			$tvalue = PhpDocUtils::findFirstTagByName('value', $doc);
			# FIXME : lucky
			# lock this file due writing
			$s = file_get_contents($this->source);
			$s = substr_replace($s, $svalue, $tvalue['_b'], $tvalue['_l']);
			file_put_contents($this->source, $s);
			// если длина $svalue отличается от длины строки в конфиге,
			// значит в новом файле изменились смещения '_b' и '_l' у всех параметров,
			// находящихся после обновляемого (на $dlen).
			// этот факт нужно синхронизировать в $this->phpdoc.
			$dlen = strlen($tvalue['value']) - strlen($svalue);
			if ($dlen !== 0) {
				# FIXME : lucky : 2008-03-09
				# сейчас, парсим файл заново и инициализируемся новыми значениями.
				# по всей видимости, есть лучшие решения.
				$this->phpdoc=NULL; $this->init(true);
			}
			$this->keys[$key] = $value;
		}
	} // end of method set
	public function listParams ()
	{
		return $this->keys;
	} // end of method list
	public function describeParam ($key)
	{
		return $this->phpdoc_keys[$key];
	} // end of method describe
	public function describe ()
	{
		return $this->phpdoc['file'];
	} // end of method describe
}

